﻿using UnityEngine;
using System.Collections;
using OhioState.Tiling;

public class GameObjTilingBuilder : ITilingBuilder<GameObject>
{
    public GameObject Template;
    public TileIndex2D Dimensions;
    public bool RandDir;
    private GameObjTileSet TileSet;
    private GameObjTiling Tiling;

    public GameObjTilingBuilder(GameObject templateTile, int width, int height)
    {
        Template = templateTile;
        Dimensions = new TileIndex2D(width, height);
        Tiling = new GameObjTiling(Dimensions.X, Dimensions.Z);
    }

    public void Build(bool rand)
    {
        RandDir = rand;
        BuildTiling();
    }

    public ITiling<GameObject> BuildTiling()
    {
        if (Tiling.GetTile(new TileIndex2D(0, 0)) != null) RemoveOldTiling();

        for (int i = 0; i < Dimensions.Z; i++)
        {
            for (int j = 0; j < Dimensions.X; j++)
            {
                TileIndex2D loc = new TileIndex2D(j, i);
                GameObjTile tile = MakeNewTile(loc);
                Tiling.SetTile(loc, tile);
            }
        }
        return Tiling;
    }

    public ITiling<GameObject> BuildTiling(GameObjTileSet tileSet)
    {
        TileSet = tileSet;

        if (Tiling.GetTile(new TileIndex2D(0, 0)) != null) RemoveOldTiling();
        PopulateTiling();
        FillInRemainingTiles();

        return Tiling;
    }

    private GameObjTile MakeNewTile(TileIndex2D loc)
    {
        GameObject tileInScene = (GameObject)GameObject.Instantiate(Template);
        GameObjTile tile = new GameObjTile(tileInScene);
        PlaceInScene(tile, loc);
        return tile;
    }

    private void PlaceInScene(GameObjTile tile, TileIndex2D loc)
    {
        Vector3 pos = new Vector3((Dimensions.X / 2f - loc.X) * tile.Width, 0, (Dimensions.Z / 2f - loc.Z) * tile.Height);
        tile.TileData.transform.position = pos;
        DetermineDirection(tile, loc);
        tile.TileData.transform.SetParent(GameObject.Find("Tiling Generator").transform);
    }

    private void DetermineDirection(GameObjTile tile, TileIndex2D loc)
    {
        int dir = 0;
        if (RandDir)
        {
            dir = (int)(4f * RandomFromDistribution.RandomLinear(1));
        }
        //else
        //{
        //    dir = 2 * loc.X + loc.Z;
        //}
        tile.TileData.transform.Rotate(0, dir * 90f, 0);
    }

    private void RemoveOldTiling()
    {
        for (int i = 0; i < Dimensions.Z; i++)
        {
            for (int j = 0; j < Dimensions.X; j++)
            {
                TileIndex2D loc = new TileIndex2D(j, i);
                GameObjTile tile = ((GameObjTile)Tiling.GetTile(loc));
                tile.TileData.transform.SetParent(null);
                GameObject.Destroy(tile.TileData);
            }
        }
    }

    private void PopulateTiling()
    {
        //fill in checker board pattern of tiles
        for (int i = 1; i < Dimensions.Z - 1; i++)
        {
            for (int j = 1; j < Dimensions.X - 1; j++)
            {
                if ((i + j) % 2 == 0) MakeTileFromSet(PickATile(), new TileIndex2D(j, i));
            }
        }
    }

    private void MakeTileFromSet(TileIndex2D tilePos, TileIndex2D location)
    {
        GameObjTile tile = new GameObjTile(TileSet.CreateGameObjectTile(tilePos));
        tile.tileSetIndex = tilePos;
        tile.TileData.transform.localScale = Vector3.one * .1f;
        PlaceInScene(tile, location);
        Tiling.SetTile(location, tile);
    }

    private void FillInRemainingTiles()
    {
        for (int i = 0; i < Dimensions.Z; i++)
        {
            for (int j = 0; j < Dimensions.X; j++)
            {
                TileIndex2D loc = new TileIndex2D(j, i);
                if ((i + j) % 2 == 1
                    //Edges
                    || (i % (Dimensions.Z - 1) == 0)
                    || (j % (Dimensions.X - 1) == 0)
                    ) MakeTileFromSet(PickAppropriateTile(loc), loc);
            }
        }
    }

    private TileIndex2D PickATile()
    {
        return new TileIndex2D((int)(Random.value * 4), (int)(Random.value * 4));
    }

    private TileIndex2D PickAppropriateTile(TileIndex2D index)
    {
        return FindTileWithMatchingEdges(CheckAllEdges(index));
    }

    private bool[] CheckAllEdges(TileIndex2D index)
    {
        //NESW
        bool[] edges = new bool[4];

        //Edge of the map
        if (index.Z >= Dimensions.Z - 1) edges[0] = true;
        if (index.Z <= 0) edges[2] = true;
        if (index.X >= Dimensions.X - 1) edges[1] = true;
        if (index.X <= 0) edges[3] = true;

        //since array is initialized with values = false, this is a tricky way to ensure we don't check out of bounds
        if (!edges[0])
        {
            if (Tiling.Tiling[index.X, index.Z + 1] == null) edges[0] = false;
            else
            {
                TileIndex2D tileSetIndex = Tiling.Tiling[index.X, index.Z + 1].tileSetIndex;
                edges[0] = TileSet.Edges[tileSetIndex.X, tileSetIndex.Z, GameObjTileSet.GetNumericEdge(GameObjTileSet.Edge.South)];
            }
        }
        else edges[0] = false;

        if (!edges[1])
        {
            if (Tiling.Tiling[index.X + 1, index.Z] == null) edges[1] = false;
            else
            {
                TileIndex2D tileSetIndex = Tiling.Tiling[index.X + 1, index.Z].tileSetIndex;
                edges[1] = TileSet.Edges[tileSetIndex.X, tileSetIndex.Z, GameObjTileSet.GetNumericEdge(GameObjTileSet.Edge.West)];
            }
        }
        else edges[1] = false;

        if (!edges[2])
        {
            if (Tiling.Tiling[index.X, index.Z - 1] == null) edges[2] = false;
            else
            {
                TileIndex2D tileSetIndex = Tiling.Tiling[index.X, index.Z - 1].tileSetIndex;
                edges[2] = TileSet.Edges[tileSetIndex.X, tileSetIndex.Z, GameObjTileSet.GetNumericEdge(GameObjTileSet.Edge.North)];
            }
        }
        else edges[2] = false;

        if (!edges[3])
        {
            if (Tiling.Tiling[index.X - 1, index.Z] == null) edges[3] = false;
            else
            {
                TileIndex2D tileSetIndex = Tiling.Tiling[index.X - 1, index.Z].tileSetIndex;
                edges[3] = TileSet.Edges[tileSetIndex.X, tileSetIndex.Z, GameObjTileSet.GetNumericEdge(GameObjTileSet.Edge.East)];
            }
        }
        else edges[3] = false;

        return edges;
    }

    private TileIndex2D FindTileWithMatchingEdges(bool[] edges)
    {
        TileIndex2D tile = new TileIndex2D(0, 0);

        for (int i = 0; i < 4; i++)
        {
            for (int j = 0; j < 4; j++)
            {
                bool found = true;
                for (int k = 0; k < 4; k++)
                {
                    found = found & (edges[k] == TileSet.Edges[j, i, k]);
                }

                if (found)
                {
                    tile = new TileIndex2D(j, i);
                    return tile;
                }
            }
        }

        return tile;
    }
}
